package org.example.user.domain.service;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import com.alicp.jetcache.Cache;
import com.alicp.jetcache.CacheManager;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.template.QuickConfig;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.PostConstruct;
import org.apache.commons.lang3.StringUtils;
import org.example.api.user.constant.UserOperateTypeEnum;
import org.example.api.user.response.UserOperatorResponse;
import org.example.base.exception.BizException;
import org.example.base.exception.RepoErrorCode;
import org.example.lock.DistributeLock;
import org.example.user.domain.entity.User;
import org.example.user.infrastructure.exception.UserErrorCode;
import org.example.user.infrastructure.exception.UserException;
import org.example.user.infrastructure.mapper.UserMapper;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RScoredSortedSet;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;

import static org.example.user.infrastructure.exception.UserErrorCode.DUPLICATE_TELEPHONE_NUMBER;

@Service
public class UserService extends ServiceImpl<UserMapper, User> implements InitializingBean {
    private static final String DEFAULT_NICK_NAME_PREFIX = "藏家_";

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private UserOperateStreamService userOperateStreamService;

    @Autowired
    private AuthService authService;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private CacheManager cacheManager;

    @Autowired
    private UserCacheDelayDeleteService userCacheDelayDeleteService;
    /**
     * 用户名布隆过滤器
     */
    private RBloomFilter<String> nickNameBloomFilter;

    /**
     * 邀请码布隆过滤器
     */
    private RBloomFilter<String> inviteCodeBloomFilter;

    /**
     * 邀请排行榜
     */
    private RScoredSortedSet<String> inviteRank;

    /**
     * 通过用户ID对用户信息做的缓存
     */
    private Cache<String, User> idUserCache;

    @PostConstruct
    public void init() {
        QuickConfig idQc = QuickConfig.newBuilder(":user:cache:id:")
                .cacheType(CacheType.BOTH)
                .expire(Duration.ofHours(2))
                .syncLocal(true)
                .build();
        idUserCache = cacheManager.getOrCreateCache(idQc);
    }

    @DistributeLock(keyExpression = "#telephone", scene = "USER_REGISTER")
    @Transactional(rollbackFor = Exception.class)
    public UserOperatorResponse register(String telephone,String inviteCode){
        String defaultNickName;
        String randomString;
        do{
            randomString = RandomUtil.randomString(6).toUpperCase();
            //前缀 + 6位随机数 + 手机号后四位
            defaultNickName = DEFAULT_NICK_NAME_PREFIX + randomString + telephone.substring(7, 11);
        }while(nickNameExist(defaultNickName) || inviteCodeExist(randomString));
        String inviterId = null;
        if (StringUtils.isNotBlank(inviteCode)) {
            User inviter = userMapper.findByInviteCode(inviteCode);
            if (inviter != null) {
                inviterId = inviter.getId().toString();
            }
        }
        // 正式注册用户
        User user = register(telephone, defaultNickName, telephone, randomString, inviterId);
        Assert.notNull(user, UserErrorCode.USER_OPERATE_FAILED.getCode());

        addNickName(defaultNickName);
        addInviteCode(randomString);
        updateInviteRank(inviterId);
        updateUserCache(user.getId().toString(), user);

        // 加入流水
        long streamResult = userOperateStreamService.insertStream(user, UserOperateTypeEnum.REGISTER);
        Assert.notNull(streamResult, () -> new BizException(RepoErrorCode.UPDATE_FAILED));
        UserOperatorResponse userOperatorResponse = new UserOperatorResponse();
        userOperatorResponse.setSuccess(true);
        return userOperatorResponse;
    }

    /**
     * 注册
     * @param telephone
     * @param nickName
     * @param password
     * @param inviteCode
     * @param inviterId
     * @return
     */
    private User register(String telephone, String nickName, String password, String inviteCode, String inviterId) {
        // 如果手机号已经被注册过
        if (userMapper.findByTelephone(telephone) != null) {
            throw new UserException(DUPLICATE_TELEPHONE_NUMBER);
        }
        User user = new User();
        user.register(telephone, nickName, password, inviteCode, inviterId);
        return save(user) ? user : null;
    }


    public boolean nickNameExist(String nickName) {
        //如果布隆过滤器中存在，再进行数据库二次判断
        if (this.nickNameBloomFilter != null && this.nickNameBloomFilter.contains(nickName)) {
            return userMapper.findByNickname(nickName) != null;
        }

        return false;
    }

    public boolean inviteCodeExist(String inviteCode) {
        //如果布隆过滤器中存在，再进行数据库二次判断
        if (this.inviteCodeBloomFilter != null && this.inviteCodeBloomFilter.contains(inviteCode)) {
            return userMapper.findByInviteCode(inviteCode) != null;
        }

        return false;
    }

    private boolean addNickName(String nickName) {
        return this.nickNameBloomFilter != null && this.nickNameBloomFilter.add(nickName);
    }

    private boolean addInviteCode(String inviteCode) {
        return this.inviteCodeBloomFilter != null && this.inviteCodeBloomFilter.add(inviteCode);
    }

    /**
     * 更新排名，排名规则：
     * <pre>
     *     1、优先按照分数排，分数越大的，排名越靠前
     *     2、分数相同，则按照上榜时间排，上榜越早的排名越靠前
     * </pre>
     *
     * @param inviterId
     */
    private void updateInviteRank(String inviterId) {
        if (inviterId == null) {
            return;
        }
        //1、这里因为是一个私有方法，无法通过注解方式实现分布式锁。
        //2、register方法已经加了锁，这里需要二次加锁的原因是register锁的是注册人，这里锁的是邀请人
        RLock rLock = redissonClient.getLock(inviterId);
        rLock.lock();
        try {
            //获取当前用户的积分
            Double score = inviteRank.getScore(inviterId);
            if (score == null) {
                score = 0.0;
            }

            //获取最近一次上榜时间
            long currentTimeStamp = System.currentTimeMillis();
            //把上榜时间转成小数(时间戳13位，所以除以10000000000000能转成小数)，并且倒序排列（用1减），即上榜时间越早，分数越大（时间越晚，时间戳越大，用1减一下，就反过来了）
            double timePartScore = 1 - (double) currentTimeStamp / 10000000000000L;

            //1、当前积分保留整数，即移除上一次的小数位
            //2、当前积分加100，表示新邀请了一个用户
            //3、加上“最近一次上榜时间的倒序小数位“作为score
            inviteRank.add(score.intValue() + 100.0 + timePartScore, inviterId);
        } finally {
            rLock.unlock();
        }
    }

    private void updateUserCache(String userId, User user) {
        idUserCache.put(userId, user);
    }
    @Override
    public void afterPropertiesSet() throws Exception {

    }
}
